/**
* Really Plain Gradient Color Picker in jQuery + 
* 
* Copyright (c) 2010 Alex Nolasco 
* Licensed under the MIT (MIT-LICENSE.txt)  licenses.
*/

(function($) {
    $.fn.gradientColorPicker = function(options) {
        // build main options before element iteration
        var opts = $.extend({
            showHexBox:true,
            bindOn:'mouseover',
            cssPrefix: '',
            gradient: {                
                count: 16,
                useSingleColors:true,
                fadeTo:'left'
            }            
        }, options);    
        return this.each(function() {               
            buildPicker(this, opts, buildSelector(this, opts));
        });
    };
   
    
    var hexDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
    
    /**
    *
    */    
    buildPicker = function(element, opts, selector) {
      
        //build color picker
        control = $("<div class='gradient_picker'>&nbsp;</div>");
        control.css('background-color', $(element).val());
        
        //bind click event to color picker
        control.bind(opts.bindOn, {picker:control, element:element, selector:selector}, function(event){
            toggleSelector(event);
        });

        //add the color picker section        
        $(element).after(control);

        //add even listener to input box
        $(element).bind("change", function() {
            selectedValue = toHex($(element).val());
            $(element).next(".gradient_picker").css("background-color", selectedValue);
        });
        //hide the input box
        $(element).hide();
    };
    
    /**
    *
    */
    buildSelector = function(element, o) {
        // this version requires the input box to have an id as it is reference throughout
        var inId =  $(element).attr('id');
        
        // build the selector using the input's id as a reference 
        var selector = $('<div class="' + o.cssPrefix + 'gradient_selector color_gradient_selector" id="' + inId  +  '-color-selector"></div>');
        var colors = typeof o.gradient.colors === 'undefined' ? $.fn.gradientColorPicker.defaultColors : o.gradient.colors ;
        //add color palette
        $.each(colors, function(i) {
                   
            if(o.gradient.count>1){
                var x = calculateColor('#'+this, '#ffffff', {count: o.gradient.count+1, type:[1,1,1]});
                var ip = 0;
                if(o.gradient.fadeTo==='left' && x.length>3){
                    ip = x.length-2;
                }
                for(i=0;i<x.length-1;i++){
                    swatch = $("<div class='" + o.cssPrefix + "gradient_swatch'>&nbsp;</div>");
                   
                    swatch.css("background-color",  x[(ip - i) *  ((ip >0) ? 1:-1) ]);
                    swatch.bind("click", function(e) { 
                        if(o.gradient.useSingleColors){
                            changeColor($(this).css("background-color"), element, e);
                        } else {
                            changeColor(x[0], element, e);
                        }
                    });       
                   swatch.bind("mouseover", function(e) {
                        $(this).addClass(o.cssPrefix + 'gradient_mouseover');
                    });
                    swatch.bind("mouseout", function(e) {
                        $(this).removeClass(o.cssPrefix + 'gradient_mouseover');
                    });                          
                    swatch.appendTo(selector);  
                }                
                $('<div  style="clear:both"></div>').appendTo(selector);          				                
            } else {

                swatch = $("<div class='" + o.cssPrefix + "gradient_swatch'>&nbsp;&nbsp;</div>")
                swatch.css("background-color", "#" + this);
                swatch.bind("click", function(e) { 
                    changeColor($(this).css("background-color"), element, e);
                });
                swatch.bind("mouseover", function(e) {
                    $(this).addClass(o.cssPrefix + 'gradient_mouseover');
                });
                swatch.bind("mouseout", function(e) {
                    $(this).removeClass(o.cssPrefix + 'gradient_mouseover');
                });         
                swatch.appendTo(selector);                                   
            }
        });
      

        //add HEX value field
        if(o.showHexBox===true){
            hex_field = $("<label for='color_value'>Hex</label><input type='text' size='8' id='color_value" + $(element).attr('id') + "'/>");
            hex_field.bind("keydown", function(event) {
                if (event.keyCode == 13) { changeColor($(this).val(), element, event); }
                if (event.keyCode == 27) { 
                    hideSelector($(selector).attr('id'));
                }
            });
            $("<div>&nbsp;</div>").append(hex_field).appendTo(selector);
        }

        $("body").append(selector);        
        selector.hide();        
        return selector;
    };
    
    /**
    *
    */
    checkMouse = function(event) {
        //check the click was on selector itself or on selectorOwner
        var selectorParent = $(event.target).parents('div.color_gradient_selector').length;
        if (selectorParent > 0){
            return;
        }
        hideSelector(event.data.selector.attr('id'));
    };
                                         
    /**
    *
    */
    hideSelector = function(selectorId) {
        //var selector = ;
        $(document).unbind("mousedown", checkMouse);
        $("#" + selectorId).hide();        
    };

    /**
    *
    */
    showSelector = function(selectorId, event) {
        var selector = event.data.selector;             
        var picker = event.data.picker;
        
        selector.css({
            top: picker.offset().top + (picker.outerHeight()),
            left: picker.offset().left
        });
                  
        hexColor = $(event.data.element).val();        
        event.data.selector.find('input').val(hexColor.toUpperCase());
        selector.show();

        //bind close event handler
        $(document).bind("mousedown", {selector:selector}, checkMouse);
    };

    /**
    *
    */
    toggleSelector = function(event, element) {
        var selectorId  =  event.data.element.id + '-color-selector';         
        $(event.data.selector).is(':visible') ? hideSelector(selectorId) : showSelector(selectorId, event);                                             
    };

    /**
    *
    */
    changeColor = function(value, element, event) {
        if (selectedValue = toHex(value)) {
            $(element).css("background-color", selectedValue);
            $(element).val(selectedValue).change();
            
            var selector = $('#'+$(element).attr('id') + '-color-selector');
            selector.find('input').val(selectedValue).change();
            
                
            //close the selector
            hideSelector($(element).attr('id') + '-color-selector');
        }
    };

    /**
    * converts RGB string to HEX - inspired by http://code.google.com/p/jquery-color-utils
    */
    toHex = function(color) {
       
        //valid HEX code is entered
        if (color.match(/[0-9a-fA-F]{3}$/) || color.match(/[0-9a-fA-F]{6}$/)) {
            color = (color.charAt(0) == "#") ? color : ("#" + color);
        }
        //rgb color value is entered (by selecting a swatch)
        else if (color.match(/^rgb\(([0-9]|[1-9][0-9]|[1][0-9]{2}|[2][0-4][0-9]|[2][5][0-5]),[ ]{0,1}([0-9]|[1-9][0-9]|[1][0-9]{2}|[2][0-4][0-9]|[2][5][0-5]),[ ]{0,1}([0-9]|[1-9][0-9]|[1][0-9]{2}|[2][0-4][0-9]|[2][5][0-5])\)$/)) {
            var c = ([parseInt(RegExp.$1, 10), parseInt(RegExp.$2,10), parseInt(RegExp.$3,10)]);

            var pad = function(str) {
                if (str.length < 2) {
                    for (var i = 0, len = 2 - str.length; i < len; i++) {
                        str = '0' + str;
                    }
                }
                return str;
            };
            if (c.length == 3) {
                var r = pad(c[0].toString(16)), g = pad(c[1].toString(16)), b = pad(c[2].toString(16));
                color = '#' + r + g + b;
            }
        }
        else {
            color = false;        
        }
        return color;
    };
    
    /**
    * generates a rgb value, using a hex 6 len char value
    */    
    hextorgb = function(hex){
      var rgb = [];		
	    rgb[0]=parseInt(hex.substr(1, 2), 16);
	    rgb[1]=parseInt(hex.substr(3, 2), 16);
	    rgb[2]=parseInt(hex.substr(5, 2), 16);
	    return rgb;
    };
      
    /**
    * calculateGradient for a color
    * @param startVal
    * @param endVal
    * @param count
    * @param type: array for each color. Speciefies, how the missing color should be calculated:
                      1: linear
                      2: trigonometrical 
                      3: accidentally
                      4: ordered accident
    */
    calculateGradient =  function(startVal, endVal, count, type) {
      
      var a = [],i;
      if(!type || !count) {
        return null;
      } else if (1<count && count < 3) {
        a[0] = startVal;
        a[1] = endVal;
        return a;
      } else if (count==1) {
        a[0] = endVal;
        return a;
      }
      
      switch(type) {
        case 1: //"linear"
          
          for(i = 0; i < count; i++){
            a[i] = Math.round(startVal + (endVal - startVal) * i / (count - 1));
          }
          break;
    
        case 2: //trigonometrical 
          
          for(i = 0; i < count; i++){
            a[i] = Math.round(startVal + (endVal - startVal) * ((Math.sin((-Math.PI / 2) + Math.PI * i / (count - 1)) + 1) / 2));
          }
          break;
    
        case 3: //accident
          
          for(i = 1; i < count - 1; i++){
            a[i] = Math.round(startVal + (endVal - startVal) * Math.random());
          }
          a[0]	= startVal;
          a[count - 1]	= endVal;
          break;
    
        case 4: //ordered accident
         
          for(i = 1; i < count - 1; i++){
            a[i] = Math.round(startVal + (endVal - startVal) * Math.random());
          }
          a[0]	= startVal;
          a[count - 1]	= endVal;
          if((typeof(a.sort) === "function") && (typeof(a.reverse) === "function"))
          {
            a.sort(function(a,b){
                return a-b;
            });
            if(startVal > endVal){
              a.reverse();
            }
          }
          break;
      }
      return a;
    };   
    
    /**
    * generates the hex-digits for a color.	
    */
    hex =  function(x) {
      return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
    };

    /**
    * calculates an array with hex values. 
    * @param startColor: starting color (hex-format or rgb)
    * @param endColor: ending color (hex-format or rgb)
    * @param options: defines, how the color should be generated. The options are defined
          by an object with:
          count: specifies, how many colors should be generated
          type: array for each color. Speciefies, how the missing color should be calculated:
                      1: linear
                      2: trigonometrical 
                      3: accidentally
                      4: ordered accident
    */
    calculateColor = function(startColor, endColor, options){
      var color = [];        
      var	start = hextorgb(startColor);
      var end   = hextorgb(endColor);			
      var rgb   = [];
      
      rgb[0]	= calculateGradient(start[0], end[0], options.count, options.type[0]);
      rgb[1]	= calculateGradient(start[1], end[1], options.count, options.type[1]);
      rgb[2]	= calculateGradient(start[2], end[2], options.count, options.type[2]);
      
      for(var i = 0; i < options.count; i++) {
        color[i] = "#" + hex(rgb[0][i]) + hex(rgb[1][i]) + hex(rgb[2][i]);
      }
      return color;    
    };

    // public methods
    $.fn.gradientColorPicker.addColors = function(colorArray) {
        $.fn.gradientColorPicker.defaultColors = $.fn.gradientColorPicker.defaultColors.concat(colorArray);
    };
    
    // default colors
    $.fn.gradientColorPicker.defaultColors = ['FFFF00', '008000', 'FF0000', '0000FF',  '000000'];

})(jQuery);


